/*
* Copyright 2010 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/

package org.etherframework.core
{
	import org.etherframework.core.events.DataEvent;
	
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.utils.Dictionary;
	
	import mx.core.FlexGlobals;
	import mx.core.mx_internal;
	import mx.managers.ISystemManager;
	import mx.utils.UIDUtil;
	
	use namespace mx_internal;
	
	[ExcludeClass]
	public class EtherManagerImpl implements IEtherManager
	{
		private static const PING_CONTROLLER:String = "__$etherPingController";
		
		private static const GET_TOPMOST:String = "__$etherGetTopmost";
		
		private static var instance:IEtherManager;
		
		private var managersCount:int = 0;
		
		private var controllersCount:int = 0;
		
		private var dispatcher:IEventDispatcher;
		
		private var dispatcher2:IEventDispatcher;
		
		private var sharedData:Dictionary;
		
		private var domain:Object;
		
		private var token:String;
		
		public function EtherManagerImpl()
		{
			if (instance)
				throw new Error("EtherManagerImpl() Please, use singleton instead.");
			
			sharedData = new Dictionary(true);
			dispatcher2 = new EventDispatcher();
			domain = _systemManager.loaderInfo.applicationDomain;
			token = UIDUtil.createUID();
			_systemManager.addEventListener(GET_TOPMOST, getTopmostHandler); 
		}
		
		public static function getInstance():IEtherManager
		{
			if (!instance)
			    instance = new EtherManagerImpl();
	
			return instance;
		}
		
		public function get topmost():Object
		{
			var evt:DataEvent = new DataEvent(GET_TOPMOST, true);
			var topmost:Object;
			
			systemManager.dispatchEvent(evt);
			topmost = (evt.data.systemManager.parentAllowsChild) ? evt.data : this;
			
			return topmost;
		}
		
		internal static var _systemManager:ISystemManager;
		
		public function get systemManager():ISystemManager
		{
			return _systemManager;
		}
		
		private const _controllers:Dictionary = new Dictionary(true);
		
		public function get controllers():Array
		{
			var result:Array = [];
			for each (var item:Object in _controllers)
			{
				result[item.index] = item.controller;
			}
			return result;
		}
		
		private var _managers:Dictionary = new Dictionary(true);
		
		public function get managers():Array
		{
			var result:Array = [];
			for (var item:Object in _managers)
			{
				result[_managers[item]] = item;
			}
			return result;
		}
		
		public function register(controller:IController):Boolean
		{
			if (!controller || (_controllers[controller]))
				return false;
			
			if (!controllersCount)
				wakeUp();
			
			if(!isPermitted(controller))
				return false;
			
			var tm:Object = topmost;
			var masterData:Dictionary = tm.getData();
			var masterDispatcher:IEventDispatcher = tm.getDispatcher2();
			var masterToken:String = tm.getToken();
			var token:String = UIDUtil.createUID();
			var localDispatcher:IEventDispatcher = getLocalDispatcher(controller);
			var pingHandler:Function = function(event:Event):void{
				event.stopImmediatePropagation();event["data"] = localDispatcher;};
			var info:ControllerInfo = new ControllerInfo();
			
			info.index = controllersCount++;
			info.controller = controller;
			info.domain = controller.host.systemManager.loaderInfo.applicationDomain;
			info.localData = new Dictionary(true);
			info.sharedData = masterData;
			info.systemManager = controller.host.systemManager;
			info.host = controller.host;
			info.localDispatcher = localDispatcher;
			info.globalDispatcher = dispatcher;
			info.masterDispatcher = masterDispatcher;
			info.masterToken = masterToken;
			info.token = token;
			info.pingHandler = pingHandler;
			
			controller.host.addEventListener(PING_CONTROLLER, pingHandler); 
			var bridge:IBridge = new Bridge(info);
			controller.metadata.bridge = bridge;
			controller.dependency.bridge = bridge;
			controller.wire.bridge = bridge;
			controller.feature.bridge = bridge;
			controller.metadata.initialize();
			controller.dependency.initialize();
			controller.wire.initialize();
			controller.feature.initialize();
			
			_controllers[controller] = info;
			
			var em:Object = topmost;
			if (em && (em != this))
				em.registerManager(this);
			
			controller.dispatchEvent(new Event("register"));
			
			return true;
		}
		
		public function unregister(controller:IController):Boolean
		{
			if (!controller || !_controllers[controller])
				return false;
			
			var info:ControllerInfo = _controllers[controller];
			controller.host.removeEventListener(PING_CONTROLLER, info.pingHandler);
			controller.metadata.uninitialize();
			controller.dependency.uninitialize();
			controller.wire.uninitialize();
			controller.feature.uninitialize();
			
			delete _controllers[controller];
			controllersCount--;
			
			if(!controllersCount)
			{
				var em:Object = topmost;
				if (em && (em != this))
					em.unregisterManager(this);
			}
			
			controller.dispatchEvent(new Event("unregister"));
			
			info.localData = null;
			info.sharedData = null;
			info.controller = null;
			info.domain = null;
			info.globalDispatcher = null;
			info.host = null;
			info.masterDispatcher = null;
			info.systemManager = null;
			info.masterToken = null;
			info.token = null;
			info.pingHandler = null;
			
			return true;
		}
		
		internal function registerManager(value:Object):Boolean
		{
			if (!value || !value.systemManager || _managers[value] || (value == this))
				return false;
			
			_managers[value] = managersCount++;
			
			return true;
		}
		
		internal function unregisterManager(value:Object):Boolean
		{
			if (!value || !value.systemManager || !_managers[value] || (value == this))
				return false;
			
			var result:Boolean = (delete _managers[value]); 
			if (result) managersCount--;
			
			return result;
		}
		
		private function wakeUp():void
		{
			dispatcher = new DisplayDispatcher(systemManager, 
				FlexGlobals.topLevelApplication as IEventDispatcher);
		}
		
		private function isPermitted(controller:IController):Boolean
		{
			for (var i:int = 0; i < _controllers.length; ++i)
			{
				if (IController(_controllers[i]).host == controller.host)
					return false;
			}
			
			return true;
		}
		
		internal function getData():Dictionary
		{
			return sharedData;
		}
		
		internal function getToken():String
		{
			return token;
		}
		
		internal function getDispatcher2():IEventDispatcher
		{
			return dispatcher2;
		}
		
		private function getLocalDispatcher(controller:IController):IEventDispatcher
		{
			var evt:Event = new DataEvent(PING_CONTROLLER, true);
			controller.host.dispatchEvent(evt);
			
			if (!evt.currentTarget)
			{
				evt = new DataEvent(PING_CONTROLLER, true);
				controller.host.systemManager.dispatchEvent(evt);
			}
			
			return (evt["data"] is Sprite) ? Sprite(evt["data"]).addChild(new Sprite()) : new Sprite();
		}
		
		private function getTopmostHandler(event:Event):void
		{
			event["data"] = instance;
		}
		
	}
}
